/*
 ------------------------------------------------------------------------------
	wmm - a test program for using audio on Windows.

	Copyright (C) 2011 Pierre Veber
	
	This program is free software; you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation; either version 3 of the License, or
	(at your option) any later version.
	
	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.
	
	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software Foundation,
	Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
------------------------------------------------------------------------------
*/

#include "stdafx.h"

#define AUDIO_BLOCK_SIZE	8192
#define AUDIO_BUFFER_SLOTS	3

//typedef struct _t_

static WAVEHDR* getFreeBlock(WAVEHDR* pBlocks, int* pFreeNum)
{
	WAVEHDR* pRet = NULL;
	*pFreeNum = 0;
	for (int i=0; i < AUDIO_BUFFER_SLOTS; i++)
	{
		if ( (pBlocks[i].dwBufferLength == 0) || (pBlocks[i].dwFlags & WHDR_DONE) )
		{
			pRet = &pBlocks[i];
			(*pFreeNum)++;
		}
	}
	return pRet;
}

static void initBlocks(WAVEHDR* pBlocks, LPSTR pWaveData)
{
	for (int i=0; i < AUDIO_BUFFER_SLOTS; i++)
	{
		memset(&pBlocks[i], 0, sizeof(WAVEHDR));
		// assign data block address to corresponding header
		pBlocks[i].lpData = pWaveData + (i * AUDIO_BLOCK_SIZE);
		pBlocks[i].dwUser = (DWORD)i;
	}
}


static void CALLBACK waveOutProc(
						HWAVEOUT hWaveOut, 
						UINT uMsg, 
						DWORD dwInstance, 
						DWORD dwParam1,
						DWORD dwParam2 
						)
{
	if (uMsg == WOM_DONE)
	{
		// debug!
		WAVEHDR* pBlock = (WAVEHDR*)dwParam1;
		printf("waveOutProc: block %d has finished reading.\n", pBlock->dwUser);
		// trigger free block event
		SetEvent((HANDLE)dwInstance);
	}
}


int _tmain(int argc, _TCHAR* argv[])
{
	FILE* fp;
	HWAVEOUT hWaveOut;
	WAVEFORMATEX wfx;
	WAVEHDR *pHdr, *pBlock;
	LPSTR pWaveData;
	DWORD dwCause;
	HANDLE hFreeBlock;
	size_t read;
	int freeblocks;
	int ret = 0;

	// check arguments
	if (argc < 2)
	{
		fprintf(stderr, "usage: %s <filename>\n", argv[0]);
		return 1;
	}

	// open file
	fp = _tfopen(argv[1], _T("rb"));
	if (fp == NULL)
	{
		fprintf(stderr, "%s: unable to open: %s \n", argv[0], argv[1]);
		return 2;
	}

	// create freeblock event
	hFreeBlock = CreateEvent(NULL, FALSE, TRUE, _T("FreeBlockEvent"));
	if (hFreeBlock == NULL)
	{
		fprintf(stderr, "%s: unable to create event\n", argv[0]);
		fclose(fp);
		return 3;
	}

	// open wave device
	wfx.nSamplesPerSec = 44100;
	wfx.wBitsPerSample = 16;
	wfx.nChannels= 2;
	wfx.cbSize = 0;
	wfx.wFormatTag = WAVE_FORMAT_PCM;
	wfx.nBlockAlign = (wfx.wBitsPerSample * wfx.nChannels) >> 3;
	wfx.nAvgBytesPerSec = wfx.nBlockAlign * wfx.nSamplesPerSec;
	if ( waveOutOpen(&hWaveOut, WAVE_MAPPER, &wfx, (DWORD_PTR)waveOutProc, (DWORD_PTR)hFreeBlock, CALLBACK_FUNCTION) != MMSYSERR_NOERROR )
	{
		fprintf(stderr, "%s: unable to open wave mapper device\n", argv[0]);
		fclose(fp);
		return 4;
	}

	// allocate memory for wave headers and wave data
	pWaveData = (LPSTR)malloc(AUDIO_BLOCK_SIZE * AUDIO_BUFFER_SLOTS);
	pHdr = (WAVEHDR*)malloc(sizeof(WAVEHDR) * AUDIO_BUFFER_SLOTS);
	if ( (pWaveData == NULL) || (pHdr == NULL) )
	{
		fprintf(stderr, "%s: unable to allocate memory for audio buffers\n", argv[0]);
		fclose(fp);
		return 5;
	}
	// initialize buffer blocks
	initBlocks(pHdr, pWaveData);

	// audio playback :
	// 
	pBlock = NULL;
	while (1)
	{
		printf("Begin audio loop.\n");
		
		// get last free block and number of free blocks
		do
		{
			pBlock = getFreeBlock(pHdr, &freeblocks);
			if (pBlock == NULL)	
			{
				printf("No free blocks, wait for free block event.\n");
				dwCause = WaitForSingleObject(hFreeBlock, INFINITE);
				if (dwCause != WAIT_OBJECT_0)
				{
					// event error
					fprintf(stderr, "%s: event error!\n", argv[0]);
					ret = 9;
					goto end;
				}
				printf("Event free block received.\n");
			}
		} while (pBlock == NULL);

		// control end of playback
		if ( (freeblocks == AUDIO_BUFFER_SLOTS) && (fp == NULL) )
		{
			ret = 0;
			goto end;
		}

		// if a block is free and file is still opened
		if ( (freeblocks > 0) && (fp != NULL) )
		{
			// "unprepare"  if needed
			if (pBlock->dwFlags & WHDR_DONE)
			{
				waveOutUnprepareHeader(hWaveOut, pBlock, sizeof(WAVEHDR));
			}
			
			// read file data
			read = fread(pBlock->lpData, sizeof(char), AUDIO_BLOCK_SIZE, fp);
			if (read < AUDIO_BLOCK_SIZE)
			{
				// means error or EOF
				if (feof(fp))
				{
					// close file
					fclose(fp);
					fp = NULL;
				}
				else
				{
					// read error
					fprintf(stderr, "%s: error reading file %s\n", argv[0], argv[1]);
					ret = 8;
					goto end;
				}
			}
			
			// write audio to device if something to write
			if (read > 0)
			{
				// prepare and send current free block to the device
				printf("Read %d bytes from file, sending block %d to the device.\n", read, pBlock->dwUser);
				pBlock->dwBufferLength = (DWORD)read;
				waveOutPrepareHeader(hWaveOut, pBlock, sizeof(WAVEHDR));
				waveOutWrite(hWaveOut, pBlock, sizeof(WAVEHDR));
			}
		}
	}

end:
	waveOutReset(hWaveOut);
	waveOutClose(hWaveOut);
	free(pWaveData);
	free(pHdr);
	if (fp != NULL)
	{
		fclose(fp);
	}

	return ret;
}
